Skip to content

Add Pandora dashboard UI shell - #126

Merged
besfeng23 merged 1 commit into
mainfrom
codex/add-pandora-dashboard-ui-shell
Jul 3, 2026
Merged

Add Pandora dashboard UI shell#126
besfeng23 merged 1 commit into
mainfrom
codex/add-pandora-dashboard-ui-shell

Conversation

@besfeng23

@besfeng23 besfeng23 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Motivation

  • Provide a clean, responsive frontend-only UI shell for the Pandora Memory Dashboard that honestly reflects the V0.4 internal memory engine state and namespace separation.
  • Offer a reviewable set of mock-driven components to iterate on layout, branding, accessibility, and client UX without adding backend wiring.

Description

  • Added a new route app/pandora/page.tsx that renders the client PandoraDashboard shell and wired responsive desktop sidebar + mobile bottom nav.
  • Introduced components under components/pandora/ (Dashboard, Sidebar, TopBar, AskPandoraHero, StatCard, WorkQueueCard, MemorySpacesCard, RecentEventsTimeline, AdaptiveProfileCard, DiagnosticsCard, MobileBottomNav, Sparkline) plus theme.ts, types.ts, and mock-data.ts.
  • Added explicit COLORS theme map and utility cn() to avoid dynamic class interpolation and ensure fixed Tailwind-like class strings, and extended app/globals.css with dashboard styles.
  • Added lucide-react to package.json for icons and kept all data mock-only; no backend/API wiring, no schema/migration changes, and no semantic/model/embedding/pruning logic added.

Testing

  • Ran npm run typecheck which passed with no type errors.
  • Ran npm run lint which completed; it surfaced pre-existing unrelated warnings in lib/db/core-repositories.ts and vitest.config.ts but no new errors.
  • Ran npm run test where the test suite completed successfully (88 files, 569 tests passed).
  • Ran npm run build which succeeded; build emitted pre-existing Next/Supabase edge/runtime warnings but completed.
  • Ran npm run env:policy which passed; attempted a Playwright screenshot but browser download failed (ERR_SOCKET_CLOSED) so no screenshot artifact was produced.

Codex Task

Summary by CodeRabbit

  • New Features

    • Added a new Pandora dashboard experience with a sidebar, top bar, search, stats, work queue, memory spaces, profile, diagnostics, and recent activity timeline.
    • Added a mobile bottom navigation layout for smaller screens.
    • Added dynamic stat cards, sparkline charts, and a profile confidence ring for richer dashboard visuals.
  • UI/Responsive Updates

    • Introduced responsive dashboard styling, including compact mobile behavior and updated pill/badge status colors.
    • Improved accessibility and interaction states across navigation and action controls.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
memory Ready Ready Preview, Comment Jul 3, 2026 5:36pm

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a new Pandora dashboard feature: a /pandora route, a full set of React components (Sidebar, TopBar, cards, timeline, sparkline, mobile nav), shared types/theme/mock-data modules, supporting dashboard CSS, and a new lucide-react dependency.

Changes

Pandora Dashboard Feature

Layer / File(s) Summary
Types, theme, and mock data foundation
components/pandora/types.ts, components/pandora/theme.ts, components/pandora/mock-data.ts, package.json
Defines ColorKey, StatItem, MemorySpace, TimelineEvent, WorkQueueData, SystemRow types; COLORS theme map and cn classname helper; mock data constants (mockStats, memorySpaces, workQueue, timelineEvents, coreSystems, gatedSystems, navItems, mobileNavItems); adds lucide-react dependency.
Reusable presentational components
components/pandora/Sparkline.tsx, StatCard.tsx, AskPandoraHero.tsx, TopBar.tsx, Sidebar.tsx, MobileBottomNav.tsx, MemorySpacesCard.tsx, WorkQueueCard.tsx, RecentEventsTimeline.tsx
Implements sparkline chart rendering, stat cards, hero banner, top bar with search, sidebar navigation, mobile bottom nav, memory space grid, work queue list, and event timeline components consuming shared theme/mock data.
Loading-aware profile and diagnostics cards
components/pandora/AdaptiveProfileCard.tsx, DiagnosticsCard.tsx
Adds ConfidenceRing-based profile card and SystemStatusRow-based diagnostics card, each toggling between loading placeholder and static content.
Dashboard composition and route wiring
components/pandora/PandoraDashboard.tsx, app/pandora/page.tsx
Composes all subcomponents into PandoraDashboard, simulates loading state with a timed effect, and renders it from the PandoraPage route.
Dashboard CSS shell and responsive styles
app/globals.css
Adds the .pd-* class set for layout, cards, pills, timeline, rings, and responsive breakpoints for mobile layout.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PandoraPage
  participant PandoraDashboard
  participant AdaptiveProfileCard
  participant DiagnosticsCard
  PandoraPage->>PandoraDashboard: render()
  PandoraDashboard->>PandoraDashboard: useEffect starts 900ms timeout
  PandoraDashboard->>AdaptiveProfileCard: render(loading=true)
  PandoraDashboard->>DiagnosticsCard: render(loading=true)
  PandoraDashboard-->>PandoraDashboard: timeout fires, isSimulatingLoad=false
  PandoraDashboard->>AdaptiveProfileCard: re-render(loading=false)
  PandoraDashboard->>DiagnosticsCard: re-render(loading=false)
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the required env-var checklist template. Replace or supplement the current text with the required checklist, and explicitly answer the env-var, Env Broker, secrets, and NEXT_PUBLIC items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding a new Pandora dashboard UI shell.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/add-pandora-dashboard-ui-shell

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f68d69c453

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/pandora/page.tsx
import { PandoraDashboard } from "@/components/pandora/PandoraDashboard";

export default function PandoraPage() {
return <PandoraDashboard />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate the Pandora dashboard route

In deployments without an external auth wall, requesting /pandora renders PandoraDashboard immediately because this page does not check the Supabase session and the repo middleware only refreshes auth cookies rather than blocking routes. Since the dashboard shows internal memory namespaces, counts, and status labels, keep this behind the same authenticated/admin boundary as the memory browser or replace it with unauthenticated-safe foundation copy.

Useful? React with 👍 / 👎.


export const mockStats: StatItem[] = [
{ id: "health", title: "Memory Health", value: "Stable", subtitle: "Ungated systems healthy", icon: ShieldCheck, color: "emerald", sparklineData: [68, 70, 72, 74, 74, 76, 78] },
{ id: "retrieval", title: "Retrieval Accuracy", value: "94.3%", subtitle: "vs last 7 days", trend: "↑ 2.4%", icon: Target, color: "indigo", sparklineData: [82, 84, 83, 88, 90, 92, 94] },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replace unsupported retrieval metric with a gated state

When /pandora renders the stat grid, this row presents “Retrieval Accuracy 94.3%” as if semantic retrieval has live eval results, while the project safety rules require retrieval not be claimed as enabled without proof and this same mock dataset later labels semantic retrieval as “Gated Off.” This can mislead operators into treating retrieval as shipped; make the card explicitly gated/planned or remove the accuracy number until it is backed by route/test evidence.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (7)
components/pandora/mock-data.ts (1)

41-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider typing navItems/mobileNavItems as readonly literal tuples.

Declaring these as const would give a literal union type (e.g., "Dashboard" | "Feed" | ...) that downstream activeNav/onNavChange state could reuse for compile-time safety, instead of plain string[].

♻️ Proposed refactor
-export const navItems = ["Dashboard", "Memory Feed", "Context Packs", "Adaptive Profiles", "Open Loops", "People", "Projects", "Retrieval Tests", "Settings"];
-export const mobileNavItems = ["Dashboard", "Feed", "Queue", "Profiles", "More"];
+export const navItems = ["Dashboard", "Memory Feed", "Context Packs", "Adaptive Profiles", "Open Loops", "People", "Projects", "Retrieval Tests", "Settings"] as const;
+export const mobileNavItems = ["Dashboard", "Feed", "Queue", "Profiles", "More"] as const;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/pandora/mock-data.ts` around lines 41 - 42, The nav item arrays
are currently inferred as mutable string arrays, which loses the literal union
type needed by downstream activeNav/onNavChange state. Update the navItems and
mobileNavItems declarations in mock-data.ts to readonly literal tuples using
const assertion so their item names can be reused as compile-time-safe unions.
Keep the existing values the same, but make the exported symbols infer literal
types instead of string[].
components/pandora/types.ts (1)

16-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded id union tightly couples the type to current mock data.

MemorySpace.id is restricted to "real_life" | "au", meaning adding a third memory space later requires editing this shared type. Since this is mock/demo data intended to grow, consider string (or a separate exported union kept in sync intentionally) if new spaces are expected soon.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/pandora/types.ts` around lines 16 - 26, The MemorySpace type
currently hardcodes id to only "real_life" | "au", which makes the shared mock
data shape hard to extend. Update MemorySpace.id in types.ts to be a more
flexible identifier such as string, or move the allowed values into a separately
exported union that can be intentionally maintained as new spaces are added.
Keep the change scoped to the MemorySpace definition so existing fields and
consumers remain compatible.
components/pandora/PandoraDashboard.tsx (1)

18-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider announcing loading completion to assistive tech.

isSimulatingLoad toggles the AdaptiveProfileCard/DiagnosticsCard content in place without any aria-live region, so screen reader users get no notification when the placeholder is replaced by real content.

Also applies to: 37-37

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/pandora/PandoraDashboard.tsx` around lines 18 - 23, The loading
state in PandoraDashboard is swapped in place without any assistive
announcement, so screen readers may miss when the placeholders become real
content. Update the PandoraDashboard component around isSimulatingLoad/useEffect
and the AdaptiveProfileCard/DiagnosticsCard render path to add an aria-live
region (or equivalent accessible status announcement) that signals when loading
completes, while keeping the timer-based transition intact.
app/globals.css (1)

197-202: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Palette duplicated six times as literal hex values.

The same 7-color palette (emerald/indigo/blue/amber/purple/red/slate) is repeated across .pd-text-*, .pd-bg-*, .pd-icon-bg-*, .pd-icon-text-*, .pd-border-*, and .pd-dot-*. Since the PR also introduces a COLORS map in theme.ts for the same palette, having two independent sources of truth risks the CSS and JS palettes drifting out of sync over time. Consider defining CSS custom properties (e.g. --pd-emerald, --pd-indigo, ...) once and referencing them across these rule groups.

♻️ Example refactor sketch
:root {
  --pd-emerald: `#10b981`; --pd-emerald-bg: `#ecfdf5`; --pd-emerald-icon-bg: `#d1fae5`;
  --pd-emerald-text: `#047857`; --pd-emerald-border: `#a7f3d0`;
  /* ...repeat for indigo/blue/amber/purple/red/slate */
}
.pd-text-emerald { color: var(--pd-emerald-text); }
.pd-bg-emerald { background: var(--pd-emerald-bg); }
.pd-icon-bg-emerald { background: var(--pd-emerald-icon-bg); }
.pd-dot-emerald { background: var(--pd-emerald); }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/globals.css` around lines 197 - 202, The palette values are duplicated in
the CSS utility classes, which creates a second source of truth alongside the
new COLORS map in theme.ts. Refactor the pd-text-*, pd-bg-*, pd-icon-bg-*,
pd-icon-text-*, pd-border-*, and pd-dot-* rules to reference shared CSS custom
properties defined once for the emerald/indigo/blue/amber/purple/red/slate
palette. Use the existing theme.ts COLORS symbols as the canonical palette
source and align the globals.css variables/rules with them so both
implementations stay in sync.
components/pandora/AskPandoraHero.tsx (1)

4-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated mock figures instead of centralized mock-data.

The evidence line hardcodes "11 memories • 6 preferences • 2 facts • Confidence 0.88" directly in JSX. The same figures (11 memories • 6 preferences • 2 facts, confidence 88) are independently hardcoded in AdaptiveProfileCard.tsx. Other components in this PR (StatCard, DiagnosticsCard) source their numbers from mock-data.ts (mockStats, coreSystems, gatedSystems), so this duplication breaks that pattern and risks the two components drifting out of sync if the mock values change.

Consider moving these shared figures into mock-data.ts and having both components read from it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/pandora/AskPandoraHero.tsx` around lines 4 - 6, The evidence text
in AskPandoraHero is hardcoding shared mock figures that are also duplicated
elsewhere, so centralize those values in mock-data.ts and consume them from both
AskPandoraHero and AdaptiveProfileCard. Update the AskPandoraHero component to
read the memory/preference/fact counts and confidence from the shared mock
source instead of embedding them in JSX, matching the existing pattern used by
StatCard and DiagnosticsCard.
components/pandora/AdaptiveProfileCard.tsx (1)

5-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded profile data duplicates AskPandoraHero's mock values.

value={88}, "11 memories • 6 preferences • 2 facts", "Last refreshed 1h ago", and the traits array are inlined here, duplicating the same figures hardcoded in AskPandoraHero.tsx. Unlike DiagnosticsCard (sourced from coreSystems/gatedSystems) or StatCard (sourced from mockStats), this component doesn't receive its content via props or mock-data.ts, making it inconsistent with the rest of the file's data-sourcing convention and prone to drifting from the hero card's numbers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/pandora/AdaptiveProfileCard.tsx` around lines 5 - 7,
AdaptiveProfileCard is hardcoding mock profile content that duplicates
AskPandoraHero’s values, so move the ring value, summary text, last-refreshed
text, and traits out of the component. Update AdaptiveProfileCard to accept this
data via props or import it from mock-data.ts, and keep the rendering logic in
AdaptiveProfileCard aligned with the data-sourcing pattern used by
DiagnosticsCard and StatCard so it stays consistent with AskPandoraHero.
components/pandora/Sidebar.tsx (1)

1-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Positional icon↔label binding is fragile.

Icon assignment relies entirely on array-index parity between icons and navItems. Any future reordering/insertion in mock-data.ts will silently desync icons from labels with no compile-time safety net. Consider keying icons by nav item name (e.g., a Record<string, LucideIcon> map) so the association is explicit and resilient to reordering.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/pandora/Sidebar.tsx` around lines 1 - 20, The Sidebar icon lookup
is currently tied to navItems array order, which makes the label-to-icon pairing
fragile. Update Sidebar to use an explicit nav item to icon mapping instead of
the icons array and index lookup, and reference the existing Sidebar, navItems,
and Icon selection logic so each item resolves by name with a safe fallback like
Circle. This should make the association resilient to reordering or inserting
items in mock-data without changing the rendered icons unexpectedly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/globals.css`:
- Line 205: The mobile layout reserve space is hardcoded in the .pd-shell rule
while .pd-mobile-nav uses env(safe-area-inset-bottom), so the bottom padding can
be too small on devices with larger safe areas. Update the responsive styles in
globals.css so .pd-shell reserves bottom space using the same safe-area-aware
calculation as .pd-mobile-nav, keeping content clear of the fixed bottom nav on
all devices.

In `@components/pandora/MobileBottomNav.tsx`:
- Line 4: The Queue attention indicator in MobileBottomNav uses an <i> element
with aria-label, which may not be announced by assistive tech. Update the
MobileBottomNav rendering so the indicator uses an accessible element or role
that supports naming, such as a status element or a visually hidden text span,
while keeping the existing item/icon logic intact.
- Line 4: The `MobileBottomNav` active-state check has dead logic and misses the
mapped Feed selection: the `(item === "Dashboard" && activeNav === "Dashboard")`
clause in `MobileBottomNav` is redundant, and the `Feed` button never becomes
active because `onNavChange` sends `"Memory Feed"` while `active` still compares
against the raw `"Feed"` label. Update the `active` calculation in
`MobileBottomNav` so it matches the same value used by `onClick` for Feed (and
keep the remaining items comparing normally), and remove the unnecessary
Dashboard-specific condition.

---

Nitpick comments:
In `@app/globals.css`:
- Around line 197-202: The palette values are duplicated in the CSS utility
classes, which creates a second source of truth alongside the new COLORS map in
theme.ts. Refactor the pd-text-*, pd-bg-*, pd-icon-bg-*, pd-icon-text-*,
pd-border-*, and pd-dot-* rules to reference shared CSS custom properties
defined once for the emerald/indigo/blue/amber/purple/red/slate palette. Use the
existing theme.ts COLORS symbols as the canonical palette source and align the
globals.css variables/rules with them so both implementations stay in sync.

In `@components/pandora/AdaptiveProfileCard.tsx`:
- Around line 5-7: AdaptiveProfileCard is hardcoding mock profile content that
duplicates AskPandoraHero’s values, so move the ring value, summary text,
last-refreshed text, and traits out of the component. Update AdaptiveProfileCard
to accept this data via props or import it from mock-data.ts, and keep the
rendering logic in AdaptiveProfileCard aligned with the data-sourcing pattern
used by DiagnosticsCard and StatCard so it stays consistent with AskPandoraHero.

In `@components/pandora/AskPandoraHero.tsx`:
- Around line 4-6: The evidence text in AskPandoraHero is hardcoding shared mock
figures that are also duplicated elsewhere, so centralize those values in
mock-data.ts and consume them from both AskPandoraHero and AdaptiveProfileCard.
Update the AskPandoraHero component to read the memory/preference/fact counts
and confidence from the shared mock source instead of embedding them in JSX,
matching the existing pattern used by StatCard and DiagnosticsCard.

In `@components/pandora/mock-data.ts`:
- Around line 41-42: The nav item arrays are currently inferred as mutable
string arrays, which loses the literal union type needed by downstream
activeNav/onNavChange state. Update the navItems and mobileNavItems declarations
in mock-data.ts to readonly literal tuples using const assertion so their item
names can be reused as compile-time-safe unions. Keep the existing values the
same, but make the exported symbols infer literal types instead of string[].

In `@components/pandora/PandoraDashboard.tsx`:
- Around line 18-23: The loading state in PandoraDashboard is swapped in place
without any assistive announcement, so screen readers may miss when the
placeholders become real content. Update the PandoraDashboard component around
isSimulatingLoad/useEffect and the AdaptiveProfileCard/DiagnosticsCard render
path to add an aria-live region (or equivalent accessible status announcement)
that signals when loading completes, while keeping the timer-based transition
intact.

In `@components/pandora/Sidebar.tsx`:
- Around line 1-20: The Sidebar icon lookup is currently tied to navItems array
order, which makes the label-to-icon pairing fragile. Update Sidebar to use an
explicit nav item to icon mapping instead of the icons array and index lookup,
and reference the existing Sidebar, navItems, and Icon selection logic so each
item resolves by name with a safe fallback like Circle. This should make the
association resilient to reordering or inserting items in mock-data without
changing the rendered icons unexpectedly.

In `@components/pandora/types.ts`:
- Around line 16-26: The MemorySpace type currently hardcodes id to only
"real_life" | "au", which makes the shared mock data shape hard to extend.
Update MemorySpace.id in types.ts to be a more flexible identifier such as
string, or move the allowed values into a separately exported union that can be
intentionally maintained as new spaces are added. Keep the change scoped to the
MemorySpace definition so existing fields and consumers remain compatible.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24cf00c0-2f9b-4b16-8574-5ce42ef16c99

📥 Commits

Reviewing files that changed from the base of the PR and between 34c3a71 and f68d69c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • app/globals.css
  • app/pandora/page.tsx
  • components/pandora/AdaptiveProfileCard.tsx
  • components/pandora/AskPandoraHero.tsx
  • components/pandora/DiagnosticsCard.tsx
  • components/pandora/MemorySpacesCard.tsx
  • components/pandora/MobileBottomNav.tsx
  • components/pandora/PandoraDashboard.tsx
  • components/pandora/RecentEventsTimeline.tsx
  • components/pandora/Sidebar.tsx
  • components/pandora/Sparkline.tsx
  • components/pandora/StatCard.tsx
  • components/pandora/TopBar.tsx
  • components/pandora/WorkQueueCard.tsx
  • components/pandora/mock-data.ts
  • components/pandora/theme.ts
  • components/pandora/types.ts
  • package.json

Comment thread app/globals.css
.pd-dot-emerald { background: #10b981; }.pd-dot-indigo { background: #6366f1; }.pd-dot-blue { background: #3b82f6; }.pd-dot-amber { background: #f59e0b; }.pd-dot-purple { background: #a855f7; }.pd-dot-red { background: #ef4444; }.pd-dot-slate { background: #64748b; }
.pd-mobile-nav { display: none; }
@media (max-width: 1200px) { .pd-stat-grid { grid-template-columns: repeat(2,minmax(0,1fr)); } .pd-dashboard-grid { grid-template-columns: 1fr; } }
@media (max-width: 900px) { .pd-shell { display: block; padding-bottom: 86px; } .pd-sidebar { display: none; } .pd-mobile-brand { display: flex; } .pd-topbar { align-items: stretch; flex-direction: column; } .pd-top-actions { justify-content: space-between; } .pd-header-row { align-items: flex-start; flex-direction: column; } .pd-mobile-nav { align-items: center; background: rgba(255,255,255,.94); border-top: 1px solid #e2e8f0; bottom: 0; display: grid; grid-template-columns: repeat(5,1fr); left: 0; padding: 10px 10px calc(env(safe-area-inset-bottom) + .75rem); position: fixed; right: 0; z-index: 30; } .pd-mobile-nav button { background: transparent; border: 0; color: #64748b; display: grid; font-size: .72rem; font-weight: 900; gap: 3px; place-items: center; } .pd-mobile-nav span { position: relative; } .pd-mobile-nav i { background: #f59e0b; border-radius: 999px; height: 7px; position: absolute; right: -5px; top: -3px; width: 7px; } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fixed padding-bottom doesn't account for device safe-area inset.

.pd-mobile-nav height grows with calc(env(safe-area-inset-bottom) + .75rem), but .pd-shell's reserved padding-bottom: 86px is a fixed value. On devices with a larger safe-area inset (e.g. iPhones with home indicator), the nav bar can be taller than 86px, causing page content to be obscured behind the fixed bottom nav.

🩹 Proposed fix
-@media (max-width: 900px) { .pd-shell { display: block; padding-bottom: 86px; } ...
+@media (max-width: 900px) { .pd-shell { display: block; padding-bottom: calc(86px + env(safe-area-inset-bottom)); } ...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@media (max-width: 900px) { .pd-shell { display: block; padding-bottom: 86px; } .pd-sidebar { display: none; } .pd-mobile-brand { display: flex; } .pd-topbar { align-items: stretch; flex-direction: column; } .pd-top-actions { justify-content: space-between; } .pd-header-row { align-items: flex-start; flex-direction: column; } .pd-mobile-nav { align-items: center; background: rgba(255,255,255,.94); border-top: 1px solid #e2e8f0; bottom: 0; display: grid; grid-template-columns: repeat(5,1fr); left: 0; padding: 10px 10px calc(env(safe-area-inset-bottom) + .75rem); position: fixed; right: 0; z-index: 30; } .pd-mobile-nav button { background: transparent; border: 0; color: #64748b; display: grid; font-size: .72rem; font-weight: 900; gap: 3px; place-items: center; } .pd-mobile-nav span { position: relative; } .pd-mobile-nav i { background: #f59e0b; border-radius: 999px; height: 7px; position: absolute; right: -5px; top: -3px; width: 7px; } }
`@media` (max-width: 900px) { .pd-shell { display: block; padding-bottom: calc(86px + env(safe-area-inset-bottom)); } .pd-sidebar { display: none; } .pd-mobile-brand { display: flex; } .pd-topbar { align-items: stretch; flex-direction: column; } .pd-top-actions { justify-content: space-between; } .pd-header-row { align-items: flex-start; flex-direction: column; } .pd-mobile-nav { align-items: center; background: rgba(255,255,255,.94); border-top: 1px solid `#e2e8f0`; bottom: 0; display: grid; grid-template-columns: repeat(5,1fr); left: 0; padding: 10px 10px calc(env(safe-area-inset-bottom) + .75rem); position: fixed; right: 0; z-index: 30; } .pd-mobile-nav button { background: transparent; border: 0; color: `#64748b`; display: grid; font-size: .72rem; font-weight: 900; gap: 3px; place-items: center; } .pd-mobile-nav span { position: relative; } .pd-mobile-nav i { background: `#f59e0b`; border-radius: 999px; height: 7px; position: absolute; right: -5px; top: -3px; width: 7px; } }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/globals.css` at line 205, The mobile layout reserve space is hardcoded in
the .pd-shell rule while .pd-mobile-nav uses env(safe-area-inset-bottom), so the
bottom padding can be too small on devices with larger safe areas. Update the
responsive styles in globals.css so .pd-shell reserves bottom space using the
same safe-area-aware calculation as .pd-mobile-nav, keeping content clear of the
fixed bottom nav on all devices.

import { Brain, Gauge, History, ListChecks, MoreHorizontal } from "lucide-react";
import { mobileNavItems } from "./mock-data";
const icons = [Gauge, History, ListChecks, Brain, MoreHorizontal];
export function MobileBottomNav({ activeNav, onNavChange }: { activeNav: string; onNavChange: (item: string) => void }) { return <nav className="pd-mobile-nav" aria-label="Mobile Pandora navigation">{mobileNavItems.map((item, index) => { const Icon = icons[index]; const active = item === activeNav || (item === "Dashboard" && activeNav === "Dashboard"); return <button type="button" key={item} aria-current={active ? "page" : undefined} onClick={() => onNavChange(item === "Feed" ? "Memory Feed" : item)}><span><Icon size={18} aria-hidden="true" />{item === "Queue" ? <i aria-label="Queue has attention items" /> : null}</span>{item}</button>; })}</nav>; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

<i> element with aria-label may not be exposed to assistive tech.

<i> has no implicit ARIA role (generic), so an aria-label on it is not guaranteed to be announced by screen readers. Use an element/role that supports accessible naming, e.g. role="status" or a visually-hidden text span.

♿ Proposed fix
-{item === "Queue" ? <i aria-label="Queue has attention items" /> : null}
+{item === "Queue" ? <span className="pd-badge-dot" role="status" aria-label="Queue has attention items" /> : null}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function MobileBottomNav({ activeNav, onNavChange }: { activeNav: string; onNavChange: (item: string) => void }) { return <nav className="pd-mobile-nav" aria-label="Mobile Pandora navigation">{mobileNavItems.map((item, index) => { const Icon = icons[index]; const active = item === activeNav || (item === "Dashboard" && activeNav === "Dashboard"); return <button type="button" key={item} aria-current={active ? "page" : undefined} onClick={() => onNavChange(item === "Feed" ? "Memory Feed" : item)}><span><Icon size={18} aria-hidden="true" />{item === "Queue" ? <i aria-label="Queue has attention items" /> : null}</span>{item}</button>; })}</nav>; }
export function MobileBottomNav({ activeNav, onNavChange }: { activeNav: string; onNavChange: (item: string) => void }) { return <nav className="pd-mobile-nav" aria-label="Mobile Pandora navigation">{mobileNavItems.map((item, index) => { const Icon = icons[index]; const active = item === activeNav || (item === "Dashboard" && activeNav === "Dashboard"); return <button type="button" key={item} aria-current={active ? "page" : undefined} onClick={() => onNavChange(item === "Feed" ? "Memory Feed" : item)}><span><Icon size={18} aria-hidden="true" />{item === "Queue" ? <span className="pd-badge-dot" role="status" aria-label="Queue has attention items" /> : null}</span>{item}</button>; })}</nav>; }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/pandora/MobileBottomNav.tsx` at line 4, The Queue attention
indicator in MobileBottomNav uses an <i> element with aria-label, which may not
be announced by assistive tech. Update the MobileBottomNav rendering so the
indicator uses an accessible element or role that supports naming, such as a
status element or a visually hidden text span, while keeping the existing
item/icon logic intact.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Redundant active condition; "Feed" nav item never shows active state.

The clause (item === "Dashboard" && activeNav === "Dashboard") is fully subsumed by item === activeNav and is dead logic. Meanwhile, onClick maps the "Feed" item to onNavChange("Memory Feed"), but active only compares item === activeNav — since item is "Feed" and activeNav becomes "Memory Feed" after the click, the Feed button will never render as active once selected.

🐛 Proposed fix
-const active = item === activeNav || (item === "Dashboard" && activeNav === "Dashboard");
+const active = item === "Feed" ? activeNav === "Memory Feed" : item === activeNav;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function MobileBottomNav({ activeNav, onNavChange }: { activeNav: string; onNavChange: (item: string) => void }) { return <nav className="pd-mobile-nav" aria-label="Mobile Pandora navigation">{mobileNavItems.map((item, index) => { const Icon = icons[index]; const active = item === activeNav || (item === "Dashboard" && activeNav === "Dashboard"); return <button type="button" key={item} aria-current={active ? "page" : undefined} onClick={() => onNavChange(item === "Feed" ? "Memory Feed" : item)}><span><Icon size={18} aria-hidden="true" />{item === "Queue" ? <i aria-label="Queue has attention items" /> : null}</span>{item}</button>; })}</nav>; }
export function MobileBottomNav({ activeNav, onNavChange }: { activeNav: string; onNavChange: (item: string) => void }) { return <nav className="pd-mobile-nav" aria-label="Mobile Pandora navigation">{mobileNavItems.map((item, index) => { const Icon = icons[index]; const active = item === "Feed" ? activeNav === "Memory Feed" : item === activeNav; return <button type="button" key={item} aria-current={active ? "page" : undefined} onClick={() => onNavChange(item === "Feed" ? "Memory Feed" : item)}><span><Icon size={18} aria-hidden="true" />{item === "Queue" ? <i aria-label="Queue has attention items" /> : null}</span>{item}</button>; })}</nav>; }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/pandora/MobileBottomNav.tsx` at line 4, The `MobileBottomNav`
active-state check has dead logic and misses the mapped Feed selection: the
`(item === "Dashboard" && activeNav === "Dashboard")` clause in
`MobileBottomNav` is redundant, and the `Feed` button never becomes active
because `onNavChange` sends `"Memory Feed"` while `active` still compares
against the raw `"Feed"` label. Update the `active` calculation in
`MobileBottomNav` so it matches the same value used by `onClick` for Feed (and
keep the remaining items comparing normally), and remove the unnecessary
Dashboard-specific condition.

@besfeng23
besfeng23 merged commit af40c93 into main Jul 3, 2026
5 checks passed
@besfeng23
besfeng23 deleted the codex/add-pandora-dashboard-ui-shell branch July 3, 2026 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant